package com.sjs.boot.dynamic.core.config.method;


import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.sjs.boot.dynamic.core.config.method.parameter.CompoundParameterInfo;
import com.sjs.boot.dynamic.protocol.Const;
import com.sjs.boot.dynamic.util.JsonUtil;
import com.sjs.boot.dynamic.util.ValParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author uto_dev_008
 * @desc methods 对象
 * @since 2021/11/9 20:49
 */
public class Methods {
    private static final Logger LOG = LoggerFactory.getLogger(Methods.class);
    private static final String CONFIG_FILE_EXT = ".cfg";
    private static final String CONFIG_METHODS = "methods";
    private static final String CONFIG_DEFAULTSETTINGS = "defaultSettings";
    private static final Map<String, CompoundParameterInfo> types = new HashMap<String, CompoundParameterInfo>();

    private static Coder coder = new DefaultCoder();
    private static final Map<String, MethodConfig> METHODS = new HashMap<String, MethodConfig>();


    public static MethodConfig get(String name) {
        return METHODS.get(name.toLowerCase());
    }

    public static final boolean init(File path) {
        LOG.debug("Methods init from {}", path.toString());

        if (!coder.init()) {
            LOG.error("Fail to init coder");
            return false;
        }

        try {
            if (!path.exists()) {
                LOG.error("{} not exists where init in {}", path, Methods.class.getName());
                return false;
            }


            String version;
            File[] vers = path.listFiles();
            String name;
            File typeDef;

            for (File ver : vers) {
                if (!ver.isDirectory()) {
                    continue;
                }
                name = ver.getName();
                if (!name.matches("^v\\d+$")) {
                    LOG.info("{} is not a config path, vxx", name);
                    continue;
                }
                version = name.substring(1);
                LOG.info("Parse version {}", version);

                if (!readOnePath(version, ver)) {
                    LOG.error("Fail to parse version:{}", version);
                    return false;
                }
            }

            return true;
        } finally {
            coder.destroy();
        }
    }

    private static boolean readOnePath(String version, File path) {
        File[] cfgs = path.listFiles();
        int len;
        String name;

        for (File cfg : cfgs) {
            if (cfg.isDirectory()) {
                if (!readOnePath(version, cfg)) {
                    return false;
                }
                continue;
            }
            name = cfg.getName();
            len = name.length() - CONFIG_FILE_EXT.length();
            if (name.indexOf(CONFIG_FILE_EXT) != len) {
                LOG.info("{} is not a method config file", name);
                continue;
            }

            LOG.info("Read one config file:{}", name);
            if (!readOneConfig(version, cfg)) {
                return false;
            }
        }
        return true;
    }

    @SuppressWarnings("unchecked")
    private static boolean readOneConfig(String version, File cfgFile) {
        LOG.debug("Read one method config file {}", cfgFile.getAbsolutePath());

        Map<String, Object> moduleCfg = null;
        String content = coder.decode(FileUtil.readBytes(cfgFile));
        if (!StrUtil.isEmpty(content)) {
            moduleCfg = JsonUtil.jsonToMap(JsonUtil.removeComment(content));
        }

        if (moduleCfg == null) {
            LOG.error("Fail to read config file:{}", cfgFile.getAbsolutePath());
            return false;
        }

        if (!moduleCfg.containsKey(CONFIG_METHODS)) {
            LOG.error("There are no {} in config", CONFIG_METHODS);
            return false;
        }

        Map<String, Object> defaultSettings = ValParser.getAsObject(moduleCfg, CONFIG_DEFAULTSETTINGS);
        Map<String, Object> settings;
        List<Object> oo = ValParser.getAsList(moduleCfg, CONFIG_METHODS);
        MethodConfig mc;
        String method;
        String[] names;

        for (Object o : oo) {
            if (!(o instanceof Map<?, ?>)) {
                LOG.error("Wrong method config in {}", cfgFile.getAbsolutePath());
                return false;
            }

            /**
             * 支持默认配置，可以减少配置的量
             */
            if (defaultSettings == null) {
                settings = (Map<String, Object>) o;
            } else {
                settings = new HashMap<String, Object>(defaultSettings);
                settings.putAll((Map<String, Object>) o);
            }

            if ((mc = MethodConfig.parse(version, settings)) == null) {
                return false;
            }

            names = mc.getName().trim().split("\\s*,\\s*");
            for (String n : names) {
                method = (version + n).toLowerCase();
                if (METHODS.containsKey(method)) {
                    LOG.error("Method {} already exists, in {}", method, cfgFile.getAbsoluteFile());
                    return false;
                }
                METHODS.put(method, mc);
                LOG.info("Add one method {},type is {}", method, mc.getType());
            }
            LOG.debug("\n--requester: {},  config: {}\n--processor:{},  config: {}\n--responser: {},  config: {}\n",
                    mc.getRequester().getClass().getName(),
                    mc.getRequestConfig().getClass().getName(),
                    mc.getProcessor().getClass().getName(),
                    mc.getProcessConfig().getClass().getName(),
                    mc.getResponser().getClass().getName(),
                    mc.getResponseConfig().getClass().getName()
            );
        }
        return true;
    }

    public static final void destroy() {
        LOG.info("Methods destroy");
        LOG.info("THREAD_POOL shutdown");

        MethodConfig mc;

        for (Map.Entry<String, MethodConfig> o : METHODS.entrySet()) {
            mc = o.getValue();
            LOG.info("Destroy {}", mc.getName());
            mc.getRequestConfig().destroy();
            mc.getProcessConfig().destroy();
            mc.getResponseConfig().destroy();
            mc.getRequester().destroy();
            mc.getProcessor().destroy();
            mc.getResponser().destroy();
        }
    }

    @SuppressWarnings("unchecked")
    public static final boolean loadGlobalType(String version, File cfg) {
        LOG.info("Read typedefine from {}", cfg.getAbsoluteFile());

        List<Object> tds = JsonUtil.jsonFileToList(cfg);
        for (Object o : tds) {
            if (!(o instanceof Map<?, ?>)) {
                LOG.info("Fail parse one typedefine of version:{}", version);
                return false;
            }
            CompoundParameterInfo td = CompoundParameterInfo.parse(version, (Map<String, Object>) o, null);

            types.put(version + "/" + td.getName(), td);
        }
        return true;
    }

    public static final CompoundParameterInfo getGlobalType(String version, String name) {
        return types.get(version + "/" + name);
    }

    public static final void addMethod(String ver, MethodConfig mc) {
        METHODS.put((ver + mc.getName().trim()).toLowerCase(), mc);
    }

    public interface Coder {
        public boolean init();

        public void destroy();

        public byte[] encode(String content);

        public String decode(byte[] content);
    }

    private static class DefaultCoder implements Coder {
        @Override
        public byte[] encode(String content) {
            return content.getBytes(Const.DEFAULT_CHARSET);
        }

        @Override
        public String decode(byte[] content) {
            return new String(content, Const.DEFAULT_CHARSET);
        }

        @Override
        public boolean init() {
            return true;
        }

        @Override
        public void destroy() {
        }
    }

    public static final void setCoder(Coder coder) {
        Methods.coder = coder;
    }
}
